package tester;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;

import trainer.Vocabulary;

/**
 * Uses a model to predict tags based on history standard input
 * 
 * Interactively predicts the next tag,
 * allows the user to input the actual next tag,
 * then repeats the process
 * 
 * @author J. Su
 *
 */
public class Tester {
    private final boolean isFastMode = false;   //Only prints out Accuracy/Perplexity. Reduces disk IO
    private final boolean isReadableMode = false;  //Prints out tags along with probabilities.
    private final boolean isDebuggingMode = false;  //Prints out tags before they are looked up.
    
    //Note: Only one model
    private int maxNgram = 3;
    private int[] ngramModels = {3};
    private double[] ngramWeights = {1};    

    
    public static void main(String[] args) throws FileNotFoundException {
        if (args.length < 1) {
            System.out.println("Usage: java Tester model_tmp.txt");
        }
        else {
            String filename = args[0];
            
            //Redirect standard input, for Eclipse
            //System.setIn(new FileInputStream("trainA_20.txt"));
            
            Tester tester = new Tester();
            tester.run(filename);
        }
    }
    
    
    /**
     * Interactively outputs the probability distribution given a history of tags
     * 
     * @param modelFilename - file containing the model. Must be an n-gram
     */
    public void run(String modelFilename) {
        //Read the model
        Model model = new Model(modelFilename);
        maxNgram = model.getN();
        if (maxNgram < 0) {
            System.err.println("Error reading model file.");
            System.err.println("Could not detect the n-gram model used.");
            return;
        }
        ngramModels[0] = maxNgram;  //this would have to change if we allowed multiple ngram models
        
        //Initialization
        int numCorrect = 0;
        int numActualTags = 0;
        double logLikelihood = 0; 
        
        LinkedList<String> history = new LinkedList<String>();
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String line;
        do {
            //Pad with start-of-sentence tag
            while (history.size() < maxNgram - 1) {
                history.add(Vocabulary.getStartSentenceTag());
            }
            
            //Build history terms
            //Cell i holds the history for an n-gram where n = i
            //Add 1 because n-grams only have n-1 history items
            //Add 1 because n-grams are 1-indexed
            String[] historyTermsList = new String[history.size() + 1 + 1]; 
            String historyTerms = "";
            
            int counter = 1;
            historyTermsList[counter] = historyTerms;
            counter++;
            
            for (String term : history) {
                historyTerms += term + ' ';
                historyTermsList[counter] = historyTerms;
                counter++;
            }
            
            //Make a prediction
            double bestProb = -1;
            String bestTag = "";
            for (String posTag : Vocabulary.getTags()) {
                double weightedProb = 0;
                
                //Calculate probability from multiple models
                for (int i = 0; i < ngramModels.length; i++) {
                    int n = ngramModels[i];
                    
                    if (isDebuggingMode) {
                        //Print out the tag that wasn't found in the model
                        System.out.println(historyTermsList[n] + posTag);
                    }
                    
                    double prob = model.getStaticModel(historyTermsList[n] + posTag, n);
                    weightedProb += prob * ngramWeights[i];
                }
                
                //Find the most likely tag
                if (weightedProb > bestProb) {
                    bestProb = weightedProb;
                    bestTag = posTag;
                }
                
                //Output probability for each tag
                if (!isFastMode) {
                    if (isReadableMode) {
                        System.out.print(String.format("%s:%.4f\t", historyTermsList[maxNgram] + posTag, weightedProb));
                    }
                    else {
                        System.out.print(String.format("%.4f ", weightedProb));
                    }
                }
            }
            if (!isFastMode) {
                System.out.println();
            }
            
            //Read actual tag from standard in
            try {
                line = reader.readLine();
                while (line != null && !Vocabulary.isTagValid(line)) {
                    System.out.println("You entered \"" + line + "\", which is not a valid tag.");
                    System.out.println("Valid tags are the following: ");
                    for (String posTag : Vocabulary.getTags()) {
                        System.out.print(posTag + "\t");
                    }
                    System.out.println();
                    System.out.println("Please enter a valid tag: ");
                    
                    line = reader.readLine();
                }
                
            }
            catch (IOException e) {
                break;
            }
            
            if (line != null) {
                //Check if prediction was accurate
                if (bestTag.equals(line)) {
                    numCorrect++;
                }
                numActualTags++;
                
                //Perplexity Calculations
                double actualLikelihood = model.getStaticModel(historyTermsList[maxNgram] + line, maxNgram);
				if (Double.isInfinite(Math.log(actualLikelihood))) {
					System.err.println("Impossible event is possible: " + historyTermsList[maxNgram] + line);
				}
                logLikelihood += Math.log(actualLikelihood);
                
                //Update history
                if (!line.equals(Vocabulary.getEndSentenceTag())) {
                    history.addLast(line);
                }
                else {
                    //Restart history after each sentence
                    history.clear();
                }
              
                
                while (history.size() >= maxNgram) {
                    history.removeFirst();
                }
            }
        } while (line != null);
        
        //Report prediction accuracy and perplexity
        double accuracy = ((double)numCorrect) / numActualTags;
        double averageLogLikelihood = logLikelihood / numActualTags;
        double perplexity = Math.exp(-averageLogLikelihood);
        
        //Output final metrics
        System.out.println(String.format("%f %f", accuracy, perplexity));
    }
}
